/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.view;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.ValueNode;
import org.jfonia.connect5.basics.Observer;
import org.jfonia.connect5.intervals.BasicDoubleInterval;
import org.jfonia.connect5.numerical.DoubleSumRelation;
import org.jfonia.connect5.relations.Equal;
import org.jfonia.connect5.relations.UnidirectionalEqual;
import org.jfonia.model.elements.IStaffElement;
import org.jfonia.model.elements.MusicalElement;
import org.jfonia.view.elements.IElement;
import org.jfonia.view.elements.StaffPart;
import org.jfonia.view.relations.DoubleInversionRelation;
import percussiongenerator.model.Pair;

/**
 *
 * @author Rik Bauwens
 */
public class TicksToXMapper implements Observer, Iterable<IElement>
{
        //Observers
        private Set<ITicksToXMapperObserver> observers = new LinkedHashSet<ITicksToXMapperObserver>();

        private RelationBuilder relationBuilder;
        private SequenceView sequenceView;

        private TreeMap<Integer, Map<Integer, Pair<ValueNode<Double>, IElement>>> mapper;
        //Auxiliary data structure to get the (previous) tick of every musicalelement
        private Map<IElement, Integer> elementToTicksMap;
        //Auxiliary data structure to store the x interval for each musicalelement,
        //this can differ from the x interval fro the frame, because a musicalelement
        //can span more than one frame
        private Map<IElement, BasicDoubleInterval> elementXInterval;

        public TicksToXMapper(RelationBuilder relationBuilder,
                MutableValueNode<Double> xNode, MutableValueNode<Double> widthNode,
                SequenceView sequenceView)
        {
                this.relationBuilder = relationBuilder;
                this.sequenceView = sequenceView;

                mapper = new TreeMap<Integer, Map<Integer, Pair<ValueNode<Double>, IElement>>>();
                elementToTicksMap = new HashMap<IElement, Integer>();
                elementXInterval = new HashMap<IElement, BasicDoubleInterval>();
        }

        public boolean isEmpty()
        {
                return mapper.isEmpty();
        }

        public IElement getElement(int beginTick, int index)
        {
                if(mapper.containsKey(beginTick))
                        return mapper.get(beginTick).get(index).second;
                Integer previousTick = mapper.lowerKey(beginTick);
                if(previousTick == null)
                        return null;
                return mapper.get(previousTick).get(index).second;
        }

        public IElement getElement(MusicalElement musicalElement)
        {
                return mapper.get(musicalElement.getBegin()).get(getIndex(musicalElement)).second;
        }

        public IElement getStaffPart(int tick)
        {
                if(!mapper.containsKey(tick))
                        return null;
                for(Pair<ValueNode<Double>, IElement> pair : mapper.get(tick).values())
                {
                        if(pair.second instanceof StaffPart &&
                                pair.second.getBegin() == pair.second.getEnd())
                                return pair.second;
                }
                return null;
        }

        public IElement getPreviousElement(IElement element)
        {
                Map<Integer, Pair<ValueNode<Double>, IElement>>
                        elements = mapper.get(element.getBegin());
                if(elements.containsKey(1)
                        && elements.get(1).second == element
                        && elements.containsKey(0))
                        return elements.get(0).second;
                Integer tick = mapper.lowerKey(element.getBegin());
                if(tick != null)
                {
                        elements = mapper.get(tick);
                        if(elements.containsKey(1))
                                return elements.get(1).second;
                        return elements.get(0).second;
                }
                return null;
        }

        public IElement getNextElement(IElement element)
        {
                Map<Integer, Pair<ValueNode<Double>, IElement>>
                        elements = mapper.get(element.getBegin());
                if(elements.containsKey(0)
                        && elements.get(0).second == element
                        && elements.containsKey(1))
                        return elements.get(1).second;
                Integer tick = mapper.higherKey(element.getBegin());
                if(tick != null)
                {
                        elements = mapper.get(tick);
                        if(elements.containsKey(0))
                                return elements.get(0).second;
                        return elements.get(1).second;
                }
                return null;
        }

        public int getIndex(IElement element)
        {
                if(element instanceof StaffPart)
                        return element.getBegin() == element.getEnd() ? 0 : 1;
                return 1;
        }

        private int getIndex(MusicalElement musicalElement)
        {
                if(musicalElement instanceof IStaffElement)
                        return StaffPart.isValidMusicalTime((IStaffElement) musicalElement) ? 1 : 0;
                return 1;
        }

        protected ValueNode<Double> getPreferredWidthNode(IElement element)
        {
                return getPair(element.getBegin(), element).first;
        }

        protected Set<IElement> getElements(int tick)
        {
                Set<IElement> elements = new HashSet<IElement>();
                for(Pair<ValueNode<Double>, IElement> pair : mapper.get(tick).values())
                        elements.add(pair.second);
                return elements;
        }

        private Pair<ValueNode<Double>, IElement> getPair(int beginTick, IElement element)
        {
                /*Iterator<Pair<ValueNode<Double>, IElement>> it =
                        mapper.get(beginTick);
                Pair<ValueNode<Double>, IElement> pair = it.next();
                while(it.hasNext() && pair.second != element)
                        pair = it.next();
                return pair;
                 */
                if(!mapper.containsKey(beginTick))
                        return null;

                for(Pair<ValueNode<Double>, IElement> pair : mapper.get(beginTick).values())
                {
                        if(pair.second == element)
                                return pair;
                }
                return null;
        }

        private boolean contains(int beginTick, IElement element)
        {
                if(!mapper.containsKey(beginTick))
                        return false;
                for(Pair<ValueNode<Double>, IElement> pair : mapper.get(beginTick).values())
                {
                        if(pair.second == element)
                                return true;
                }
                return false;
        }

        public void add(IElement element)
        {
                Integer beginTick = element.getBegin();
                Integer endTick = element.getEnd();
                
                //Check whether the element is already added
                //Only one element can correspond with the same musicaltime and
                //use the same interval
                if(!contains(beginTick, element))
                //Add element
                {
                        //Create a new pair and add the musicalelement and interval
                        Pair<ValueNode<Double>, IElement> pair =
                                new Pair<ValueNode<Double>, IElement>();
                        pair.first = element.getPreferredWidthNode();
                        pair.second = element;
                        if(!mapper.containsKey(beginTick))
                                mapper.put(beginTick, new HashMap<Integer, Pair<ValueNode<Double>, IElement>>());
                                //mapper.put(beginTick, new HashSet<Pair<ValueNode<Double>, IElement>>());
                        mapper.get(beginTick).put(getIndex(element), pair);
                        //mapper.get(beginTick).add(pair);

                        //Add this as observer
                        element.addObserver(this);
                        //Notify observers
                        notifyObservers(element, true);
                        //Add to auxiliary map
                        elementToTicksMap.put(element, beginTick);

                        //Dimensions
                        BasicDoubleInterval xInterval = new BasicDoubleInterval();
                        if(beginTick == endTick)
                        {
                                new UnidirectionalEqual<Double>(relationBuilder
                                        .getPreFrameX(beginTick, element),
                                        xInterval.getBeginNode());
                                new Equal<Double>(relationBuilder.getPreFrameWidth(beginTick, element),
                                        xInterval.getDifferenceNode());
                        }
                        else
                        {
                                new UnidirectionalEqual<Double>(relationBuilder.getBeginFrameX(beginTick, element), xInterval.getBeginNode());
                                MutableValueNode<Double> widthNode = new BasicValueNode<Double>(0.0);
                                MutableValueNode<Double> invertedNode = new BasicValueNode<Double>(0.0);
                                new DoubleInversionRelation(relationBuilder.getBeginFrameX(beginTick, element), invertedNode);
                                new DoubleSumRelation(relationBuilder.getEndFrameX(endTick, element), invertedNode, widthNode);
                                new Equal<Double>(widthNode, xInterval.getDifferenceNode());
                        }

                        //Add to auxiliary x interval map
                        elementXInterval.put(element, xInterval);
                        //Set the coördinates
                        element.setFrameXNode(xInterval.getBeginNode());
                        element.setFrameWidthNode(xInterval.getDifferenceNode());
                }
        }

        private void remove(IElement element)
        {
                //Previous beginTick
                Integer beginTick = elementToTicksMap.get(element);
                //Check whether the key exists
                if(contains(beginTick, element))
                {
                        //Remove from auxiliary map
                        elementToTicksMap.remove(element);
                        //Remove from auxiliary x interval map
                        elementXInterval.remove(element);
                        //Remove the entry corresponding with the (previous) tick
                        //mapper.remove(beginTick);
                        mapper.get(beginTick).remove(getIndex(element));
                        //mapper.get(beginTick).remove(getPair(beginTick, element));
                        //Remove this as observer
                        element.removeObserver(this);
                        //Notify observers
                        notifyObservers(element, false);
                }

                if(mapper.get(beginTick).isEmpty())
                        mapper.remove(beginTick);
        }

        public Iterator<IElement> iterator()
        {
                return elementXInterval.keySet().iterator();
        }

        //An element has changed
        public void onNotify(Object source)
        {
                IElement element = (IElement) source;
                //Remove element and add it again (with new tick)
                remove(element);
                if(element.getBegin() >= 0)
                        add(element);
        }

	public void addObserver(ITicksToXMapperObserver o)
	{
		if (!observers.contains(o))
			observers.add(o);
	}

	public boolean containsObserver(ITicksToXMapperObserver o)
	{
		return observers.contains(o);
	}

	public void removeObserver(ITicksToXMapperObserver o)
	{
		observers.remove(o);
	}

	public void notifyObservers(IElement element, boolean added)
	{
		for (ITicksToXMapperObserver o: observers)
			o.changed(this, element, added);
	}

	public void notifyObserversExcept(Observer except, IElement element, boolean added)
	{
		for (ITicksToXMapperObserver o: observers)
			if (o != except)
				o.changed(this, element, added);
	}
}
